1 package org.apache.solr.util;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import javax.crypto.BadPaddingException;
21 import javax.crypto.Cipher;
22 import javax.crypto.IllegalBlockSizeException;
23 import javax.crypto.spec.IvParameterSpec;
24 import javax.crypto.spec.SecretKeySpec;
25
26 import java.lang.invoke.MethodHandles;
27 import java.nio.ByteBuffer;
28 import java.nio.charset.Charset;
29 import java.nio.charset.StandardCharsets;
30 import java.security.GeneralSecurityException;
31 import java.security.InvalidKeyException;
32 import java.security.KeyFactory;
33 import java.security.KeyPairGenerator;
34 import java.security.MessageDigest;
35 import java.security.NoSuchAlgorithmException;
36 import java.security.PrivateKey;
37 import java.security.PublicKey;
38 import java.security.SecureRandom;
39 import java.security.Signature;
40 import java.security.SignatureException;
41 import java.security.spec.X509EncodedKeySpec;
42 import java.util.Arrays;
43 import java.util.HashMap;
44 import java.util.Map;
45
46 import org.apache.solr.common.SolrException;
47 import org.apache.solr.common.util.Base64;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50
51
52
53
54 public final class CryptoKeys {
55 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
56 private final Map<String, PublicKey> keys;
57 private Exception exception;
58
59 public CryptoKeys(Map<String, byte[]> trustedKeys) throws Exception {
60 HashMap<String, PublicKey> m = new HashMap<>();
61 for (Map.Entry<String, byte[]> e : trustedKeys.entrySet()) {
62 m.put(e.getKey(), getX509PublicKey(e.getValue()));
63
64 }
65 this.keys = m;
66 }
67
68
69
70
71 public String verify(String sig, ByteBuffer data) {
72 exception = null;
73 for (Map.Entry<String, PublicKey> entry : keys.entrySet()) {
74 boolean verified;
75 try {
76 verified = CryptoKeys.verify(entry.getValue(), Base64.base64ToByteArray(sig), data);
77 log.info("verified {} ", verified);
78 if (verified) return entry.getKey();
79 } catch (Exception e) {
80 exception = e;
81 log.info("NOT verified ");
82 }
83
84 }
85
86 return null;
87 }
88
89
90
91
92
93 public static PublicKey getX509PublicKey(byte[] buf)
94 throws Exception {
95 X509EncodedKeySpec spec = new X509EncodedKeySpec(buf);
96 KeyFactory kf = KeyFactory.getInstance("RSA");
97 return kf.generatePublic(spec);
98 }
99
100
101
102
103
104
105
106
107 public static boolean verify(PublicKey publicKey, byte[] sig, ByteBuffer data) throws InvalidKeyException, SignatureException {
108 int oldPos = data.position();
109 Signature signature = null;
110 try {
111 signature = Signature.getInstance("SHA1withRSA");
112 signature.initVerify(publicKey);
113 signature.update(data);
114 boolean verify = signature.verify(sig);
115 return verify;
116
117 } catch (NoSuchAlgorithmException e) {
118
119 } finally {
120
121 data.position(oldPos);
122 }
123 return false;
124 }
125
126 private static byte[][] evpBytesTokey(int key_len, int iv_len, MessageDigest md,
127 byte[] salt, byte[] data, int count) {
128 byte[][] both = new byte[2][];
129 byte[] key = new byte[key_len];
130 int key_ix = 0;
131 byte[] iv = new byte[iv_len];
132 int iv_ix = 0;
133 both[0] = key;
134 both[1] = iv;
135 byte[] md_buf = null;
136 int nkey = key_len;
137 int niv = iv_len;
138 int i = 0;
139 if (data == null) {
140 return both;
141 }
142 int addmd = 0;
143 for (; ; ) {
144 md.reset();
145 if (addmd++ > 0) {
146 md.update(md_buf);
147 }
148 md.update(data);
149 if (null != salt) {
150 md.update(salt, 0, 8);
151 }
152 md_buf = md.digest();
153 for (i = 1; i < count; i++) {
154 md.reset();
155 md.update(md_buf);
156 md_buf = md.digest();
157 }
158 i = 0;
159 if (nkey > 0) {
160 for (; ; ) {
161 if (nkey == 0)
162 break;
163 if (i == md_buf.length)
164 break;
165 key[key_ix++] = md_buf[i];
166 nkey--;
167 i++;
168 }
169 }
170 if (niv > 0 && i != md_buf.length) {
171 for (; ; ) {
172 if (niv == 0)
173 break;
174 if (i == md_buf.length)
175 break;
176 iv[iv_ix++] = md_buf[i];
177 niv--;
178 i++;
179 }
180 }
181 if (nkey == 0 && niv == 0) {
182 break;
183 }
184 }
185 for (i = 0; i < md_buf.length; i++) {
186 md_buf[i] = 0;
187 }
188 return both;
189 }
190
191 public static String decodeAES(String base64CipherTxt, String pwd) {
192 int[] strengths = new int[]{256, 192, 128};
193 Exception e = null;
194 for (int strength : strengths) {
195 try {
196 return decodeAES(base64CipherTxt, pwd, strength);
197 } catch (Exception exp) {
198 e = exp;
199 }
200 }
201 throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Error decoding ", e);
202 }
203
204
205 public static String decodeAES(String base64CipherTxt, String pwd, final int keySizeBits) {
206 final Charset ASCII = Charset.forName("ASCII");
207 final int INDEX_KEY = 0;
208 final int INDEX_IV = 1;
209 final int ITERATIONS = 1;
210 final int SALT_OFFSET = 8;
211 final int SALT_SIZE = 8;
212 final int CIPHERTEXT_OFFSET = SALT_OFFSET + SALT_SIZE;
213
214 try {
215 byte[] headerSaltAndCipherText = Base64.base64ToByteArray(base64CipherTxt);
216
217
218
219 byte[] salt = Arrays.copyOfRange(
220 headerSaltAndCipherText, SALT_OFFSET, SALT_OFFSET + SALT_SIZE);
221 byte[] encrypted = Arrays.copyOfRange(
222 headerSaltAndCipherText, CIPHERTEXT_OFFSET, headerSaltAndCipherText.length);
223
224
225
226 Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
227 MessageDigest md5 = MessageDigest.getInstance("MD5");
228
229
230
231
232 final byte[][] keyAndIV = evpBytesTokey(
233 keySizeBits / Byte.SIZE,
234 aesCBC.getBlockSize(),
235 md5,
236 salt,
237 pwd.getBytes(ASCII),
238 ITERATIONS);
239
240 SecretKeySpec key = new SecretKeySpec(keyAndIV[INDEX_KEY], "AES");
241 IvParameterSpec iv = new IvParameterSpec(keyAndIV[INDEX_IV]);
242
243
244
245 aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
246 byte[] decrypted = aesCBC.doFinal(encrypted);
247 return new String(decrypted, ASCII);
248 } catch (BadPaddingException e) {
249
250 throw new IllegalStateException(
251 "Bad password, algorithm, mode or padding;" +
252 " no salt, wrong number of iterations or corrupted ciphertext.", e);
253 } catch (IllegalBlockSizeException e) {
254 throw new IllegalStateException(
255 "Bad algorithm, mode or corrupted (resized) ciphertext.", e);
256 } catch (GeneralSecurityException e) {
257 throw new IllegalStateException(e);
258 }
259 }
260
261 public static PublicKey deserializeX509PublicKey(String pubKey) {
262 try {
263 KeyFactory keyFactory = KeyFactory.getInstance("RSA");
264 X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.base64ToByteArray(pubKey));
265 return keyFactory.generatePublic(publicKeySpec);
266 } catch (Exception e) {
267 throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
268 }
269 }
270
271 public static byte[] decryptRSA(byte[] buffer, PublicKey pubKey) throws InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
272 Cipher rsaCipher = null;
273 try {
274 rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
275 } catch (Exception e) {
276 throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
277 }
278 rsaCipher.init(Cipher.DECRYPT_MODE, pubKey);
279 return rsaCipher.doFinal(buffer, 0, buffer.length);
280
281 }
282
283 public static class RSAKeyPair {
284 private final String pubKeyStr;
285 private final PublicKey publicKey;
286 private final PrivateKey privateKey;
287 private final SecureRandom random = new SecureRandom();
288
289
290 public RSAKeyPair() {
291 KeyPairGenerator keyGen = null;
292 try {
293 keyGen = KeyPairGenerator.getInstance("RSA");
294 } catch (NoSuchAlgorithmException e) {
295 throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
296 }
297 keyGen.initialize(512);
298 java.security.KeyPair keyPair = keyGen.genKeyPair();
299 privateKey = keyPair.getPrivate();
300 publicKey = keyPair.getPublic();
301
302 X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
303 publicKey.getEncoded());
304
305 pubKeyStr = Base64.byteArrayToBase64(x509EncodedKeySpec.getEncoded());
306 }
307
308 public String getPublicKeyStr() {
309 return pubKeyStr;
310 }
311
312 public PublicKey getPublicKey() {
313 return publicKey;
314 }
315
316 public byte[] encrypt(ByteBuffer buffer) {
317 try {
318 Cipher rsaCipher = Cipher.getInstance("RSA/ECB/nopadding");
319 rsaCipher.init(Cipher.ENCRYPT_MODE, privateKey);
320 return rsaCipher.doFinal(buffer.array(),buffer.position(), buffer.limit());
321 } catch (Exception e) {
322 throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,e);
323 }
324 }
325 public byte[] signSha256(byte[] bytes) throws InvalidKeyException, SignatureException {
326 Signature dsa = null;
327 try {
328 dsa = Signature.getInstance("SHA256withRSA");
329 } catch (NoSuchAlgorithmException e) {
330 throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
331 }
332 dsa.initSign(privateKey);
333 dsa.update(bytes,0,bytes.length);
334 return dsa.sign();
335
336 }
337
338 }
339
340 public static void main(String[] args) throws Exception {
341 RSAKeyPair keyPair = new RSAKeyPair();
342 System.out.println(keyPair.getPublicKeyStr());
343 PublicKey pk = deserializeX509PublicKey(keyPair.getPublicKeyStr());
344 byte[] payload = "Hello World!".getBytes(StandardCharsets.UTF_8);
345 byte[] encrypted = keyPair.encrypt(ByteBuffer.wrap(payload));
346 String cipherBase64 = Base64.byteArrayToBase64(encrypted);
347 System.out.println("encrypted: "+ cipherBase64);
348 System.out.println("signed: "+ Base64.byteArrayToBase64(keyPair.signSha256(payload)));
349 System.out.println("decrypted "+ new String(decryptRSA(encrypted , pk), StandardCharsets.UTF_8));
350 }
351
352 }